//*************************************************************************************
// EGTB Generator Main Building Header File
//*************************************************************************************

#pragma once

#include "BuildDb.h"

// Templated functions
namespace EndGameDB
{

	 // Returns the disk space took by one database slice
	////////////////////////////////////////////////////////////////////////////////////////////////////////////

	template<int MaxField, int BitsPerRow> 
	uint64 GetDiskSpace(int wm, int wk, int bm, int bk, int wmrank, int bmrank, uint64& max, 
							uint64& wdlSpace, uint64& dtmSpace, int& count)
	{
		// WDL db size
		uint64 space = GetDatabaseSize<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank);
		space = Pad16(space / 4);

		wdlSpace += space;

		// DTM db size
		uint64 space2 = GetDatabaseSize<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank);

		space += space2;
		dtmSpace += space2;

		count++;

		int symmetric = IsSymmetric(wm, wk, bm, bk, wmrank, bmrank);
		if(!symmetric)
		{
			wdlSpace += space;
			dtmSpace += space2;
			space += space;
			count++;
		}

		if(max < space)
			max = space;

		// return 
		return space;
	}


	 // Returns the subdatabase count and the amount of disk space
	////////////////////////////////////////////////////////////////////////////////////////////////////////////

	template<int BitsPerRow, int MaxRank, int MaxField> 
	int GetSubDbCount(int maxStones, uint64 *diskSpace, uint64* maxSubDbSize, 
					uint64* pWDLSpace = NULL, uint64* pDTMSpace = NULL, bool bOnlyMaximumDb = false)
	{
		int count = 0;
		uint64 space = 0;
		uint64 wdlSpace = 0;
		uint64 dtmSpace = 0;
		uint64 max = 0;

		

		for(int n=2; n<= maxStones; n++)
		{
			// if we are calculating space for the biggest database
			if(bOnlyMaximumDb)
				n = maxStones;

			int bm=0,bk=0,wm=0,wk=0;
			int div1, div2, div3;
	
			for(div1=0;div1<=n;div1++)
			{
				for(div2=div1;div2<=n;div2++)
				{
					for(div3=div2;div3<=n;div3++)
					{
						wm=div1;
						wk=div2-div1;
						bm=div3-div2;
						bk=n-div3;

						int bmrank, wmrank; 
	
						if((bm+bk==0) || (wm+wk==0)) 
							continue;

						if(wm+wk<bm+bk)
							continue;

						if(wm+wk>MAXPIECE)
							continue;

						if(wk+wm == bk+bm)
						{
							if(bk>wk)
								continue;
						}

						if(wm == 0 && bm == 0)
							space += GetDiskSpace<MaxField, BitsPerRow>(wm, wk, bm, bk, 0, 0, max, wdlSpace, dtmSpace, count);
	
						if(wm!=0 && bm==0)
						{
							for(wmrank=MaxRank-1;wmrank>=((wm-1)/BitsPerRow);wmrank--)
								space += GetDiskSpace<MaxField, BitsPerRow>(wm, wk, bm, bk, wmrank, 0, max, wdlSpace, dtmSpace, count);
						}
	
						if(bm!=0 && wm==0)
						{
							for(bmrank=MaxRank-1;bmrank>=((bm-1)/BitsPerRow);bmrank--)
								space += GetDiskSpace<MaxField, BitsPerRow>(wm, wk, bm, bk, 0, bmrank, max, wdlSpace, dtmSpace, count);
						}
	
						if(wm!=0 && bm!=0)
						{
							for(wmrank=MaxRank-1;wmrank>=((wm-1)/BitsPerRow);wmrank--)
							{
								for(bmrank=MaxRank-1;bmrank>=((bm-1)/BitsPerRow);bmrank--)
									space += GetDiskSpace<MaxField, BitsPerRow>(wm, wk, bm, bk, wmrank, bmrank, max, wdlSpace, dtmSpace, count);
							}
						}

					}
				}
			}
		}

		if(diskSpace != NULL)
			*diskSpace = space;

		if(maxSubDbSize != NULL)
			*maxSubDbSize = max;
		
		if(pWDLSpace != NULL)
			*pWDLSpace = wdlSpace;

		if(pDTMSpace != NULL)
			*pDTMSpace = dtmSpace;

		return count;
	}


	 // BuildSubDbSlice - The main function building the template size
	////////////////////////////////////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxField, int BitsPerRow, int MaxRank> 
	int BuildSubDbSlice(int wm, int wk, int bm, int bk, int wmrank, int bmrank)
	{
		bool symmetric = false;	// is the database symmetric?
		int64 dbsize;		// size of the database in number of BitBoards
		bool present = false;
		int done=0, pass=0;
		FILE *fp;
		int loadblack = 0, loadwhite = 0;
		int state = 0;

		// call callback progress
		if(OnBuildSubDbStartedPtr != NULL)
		{
			OnBuildSubDbStartedPtr( wm, wk, bm, bk, wmrank, bmrank );
		}
	

		// validation
		if(wm==bm && wk==bk)
			{
			if(bmrank>wmrank)
				return 0;
			}

		dbsize = GetDatabaseSize<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank);
		symmetric = IsSymmetric(wm, wk, bm, bk, wmrank, bmrank);

		//-----------------------------------------------------------------------------------------------------
		// check if we already calculated this database if #USEOLD is defined
		// if yes, just return - if no, continue
		//-----------------------------------------------------------------------------------------------------
	
		present = CheckDbPresence(wm, wk, bm, bk, wmrank, bmrank);
		if(present)
		{
			return 1;
		}

		if(wm == 0 && wk == 1 && bm == 1 && bk == 0)
		{
			int iii = 5;
		}

		 // check recovery from termination
		////////////////////////////////////////////////////////////////////////////
		state = FindMaximumState(wm, wk, bm, bk, wmrank, bmrank, 10);
		if(state < 0)
			state = 0;
		else
		{
			LoadDBState<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank, &currentDb_w, &currentDb_b, &dtm_W, &dtm_B, state);

			PRINT(L"Recovering from state %d", state);

			int oldstate = state;
			state++;

			switch(oldstate)
			{
			case 0:
				goto state1;
			case 1:
				goto state2;
			case 2:
				goto state3;
			case 3:
				goto state4;
			case 4:
				goto state5;
			case 5:
				state--;
				goto state5;
			case 6:
				goto state6;
			case 7:
				goto state7;
			}
		}

		PRINT(L"Allocate DB");
		PROCESS_STARTED(L"Allocate DB [1/11]");
		AllocateNewSubDatabase<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank);
	
	
		//-----------------------------------------------------------------------------------------------------
		// STEP 1: initialize to 0 = UNKNOWN
		//-----------------------------------------------------------------------------------------------------
		PRINT(L"SetDbToUNKNOWN");
		PROCESS_STARTED(L"SetDbToUNKNOWN [2/11]");

		WATCH_START(T_DB_UNKNOWN);

		SetDbToUNKNOWN<BitsPerRow, MaxField>(currentDb_w, currentDb_b, dtm_W, dtm_B, wm, wk, bm, bk, wmrank, bmrank);

		WATCH_END(T_DB_UNKNOWN);
	
		SAVE_STATE();
		state++;

		//-----------------------------------------------------------------------------------------------------
		// 1b: set impossible BitBoards to a WIN - i'm setting them to a known value, since the database
		// propagation pass checks for UNKNOWN or DRAWn BitBoards to loop over - it can continue when it sees
		// a LOSS or a WIN
		//-----------------------------------------------------------------------------------------------------

	state1:; // 0
		PRINT(L"SetImpossibleTo");
		PROCESS_STARTED(L"SetImpossibleTo [3/11]");

		WATCH_START(T_DB_SET_IMPOSSIBLE	);

		SetImpossibleTo<BB, MaxRank, BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank, currentDb_w, currentDb_b, dtm_W, dtm_B, WIN);

		WATCH_END(T_DB_SET_IMPOSSIBLE);

		SAVE_STATE();
		state++;

		//-----------------------------------------------------------------------------------------------------
		// STEP 2: "seed pass": resolve captures 
		//-----------------------------------------------------------------------------------------------------
	state2:; // 1
		PRINT(L"ResolveCaptures....");
		PROCESS_STARTED(L"ResolveCaptures [4/11]");

		WATCH_START(T_DB_CAPTURES);

		ResolveCaptures<BB, MaxField, BitsPerRow>(wm, wk, bm, bk, wmrank, bmrank, currentDb_w, currentDb_b, dtm_W, dtm_B);

		WATCH_END(T_DB_CAPTURES);
	
		SAVE_STATE();
		state++;

		//-----------------------------------------------------------------------------------------------------
		//  STEP 3:		do an IO pass as in chinook paper: only look at moves which convert
		//				into databases we have already calculated, i.e. moves which increas
		//				the leading checker rank or promote. if such a move leads to a WIN,
		//				save it as a win, if it leads to a DRAW, we save it as a DRAW, but
		//				keep in mind that a DRAW does not mean the BitBoard is resolved, 
		//				just that it is AT LEAST a DRAW
		//				if there are no men for the side to move, then there is no point in doing 
		//				this - you cannot convert to another database
		//-----------------------------------------------------------------------------------------------------
	state3:; // 2
		if(wm)
		{
			PRINT(L"IOPassWhite...");
			PROCESS_STARTED(L"IOPassWhite [5/11]");

			WATCH_START(T_DB_PASS_WHITE);
			IOPassWhite<BB, MaxField, BitsPerRow>(currentDb_w, dtm_W, wm, wk, bm, bk, wmrank, bmrank);
			WATCH_END(T_DB_PASS_WHITE);
		}

		SAVE_STATE();
		state++;
			
	state4:; // 3
		if(!symmetric && bm)
		{
			PRINT(L"IOPassBlack...");
			PROCESS_STARTED(L"IOPassBlack [6/11]");

			WATCH_START(T_DB_PASS_BLACK);
			IOPassBlack<BB, MaxField, BitsPerRow>(currentDb_b, dtm_B, wm, wk, bm, bk, wmrank, bmrank);
			WATCH_END(T_DB_PASS_BLACK);
		}

		SAVE_STATE();
		state++;
	
		//-----------------------------------------------------------------------------------------------------
		// STEP 4:	do a series of propagation passes until no new values are found
		//			here, only WINS and LOSSES can be resolved. DRAWS are meaningless
		//          in the "noio" framework and can not be propagated
		//			!! this loop takes most of the time of builddb. optimize it !!
		//-----------------------------------------------------------------------------------------------------

	state5:; // 4,5

		int passes = 1;
		while(!done)
			{
			PRINT(L"PropagateWhite: pass = %d", passes);
			PROCESS_STARTED(L"PropagateWhite: pass = %d [7/11]", passes);

			WATCH_START(T_DB_PROPAGATE_WHITE);
			// 4a: propagation for BLACK
			done = PropagateWhite<BB, MaxField, BitsPerRow>(currentDb_w, dtm_W, wm, wk, bm, bk, wmrank, bmrank);
			WATCH_END(T_DB_PROPAGATE_WHITE);

			//-----------------------------------------------------------------------------------------------------
			// 4b: propagation pass for WHITE
			//-----------------------------------------------------------------------------------------------------
			if(!symmetric)
			{
				PRINT(L"PropagateBlack: pass = %d", passes);
				PROCESS_STARTED(L"PropagateBlack: pass = %d [8/11]", passes);

				WATCH_START(T_DB_PROPAGATE_BLACK)
				done &= PropagateBlack<BB, MaxField, BitsPerRow>(currentDb_b, dtm_B, wm, wk, bm, bk, wmrank, bmrank);
				WATCH_END(T_DB_PROPAGATE_BLACK);
			}

			passes++;

			SAVE_STATE();
		}

		//if(wk == 2 && wm == 1 && bk == 1 && wmrank == 6)
		//{
		//	int logId = LogInit("c:\\unknownpositions.log");
		//	for(int64 i = 0; i < dbsize; i++)
		//	{
		//		if(GetDatabaseValue(currentDb_b, i) == UNKNOWN)
		//		{
		//			BitBoard pUnk;
		//			IndexToBitBoard(i, &pUnk, wm, wk, bm, bk, wmrank, bmrank);
		//			LogBB(logId, pUnk);			
		//		}
		//	}
		//}


		state++;
		SAVE_STATE();
		state++;


		//-----------------------------------------------------------------------------------------------------
		// STEP 5: set all unknown values to draw
		//-----------------------------------------------------------------------------------------------------
	state6:; // 6

		PRINT(L"SetUnknownToDraw....");
		PROCESS_STARTED(L"SetUnknownToDraw [9/11]");

		WATCH_START(T_DB_UNKNOWN_TO_DRAW);
	
		SetUnknownToDraw(currentDb_w, dtm_W, dbsize);
		if(!symmetric)
			SetUnknownToDraw(currentDb_b, dtm_B, dbsize);

		WATCH_END(T_DB_UNKNOWN_TO_DRAW);

		SAVE_STATE();
		state++;

		//-----------------------------------------------------------------------------------------------------
		// STEP 6: set all impossible BitBoards to UNKNOWN
		//-----------------------------------------------------------------------------------------------------
	state7:; // 7

		PRINT(L"SetImpossibleTo....");
		PROCESS_STARTED(L"SetImpossibleTo [10/11]");

		WATCH_START(T_DB_SET_IMPOSSIBLE);
		SetImpossibleTo<BB, MaxRank, BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank, currentDb_w, currentDb_b, dtm_W, dtm_B, UNKNOWN);
		WATCH_END(T_DB_SET_IMPOSSIBLE);


		//-----------------------------------------------------------------------------------------------------
		// STEP 7: save file to disk
		//-----------------------------------------------------------------------------------------------------

	
		PROCESS_STARTED(L"Saving [11/11]");

		WATCH_START(T_FILE_WRITE);
	
		// TODO: stuff into savetodisk()
		wchar_t dbname[PATH_MAX];

		// save databases
		int64 memsize = Pad16((GetDatabaseSize<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank)) / 4);

		SetDbName(wm, wk, bm, bk, wmrank, bmrank, WHITE, dbname);
		int error;
		if(_wfopen_s(&fp, dbname, L"wb") != 0)
		{
			_get_errno(&error);
		}

		fwrite(currentDb_w, 1, (size_t)memsize, fp);
		fclose(fp);

		if(!symmetric)
		{
			SetDbName(wm, wk, bm, bk, wmrank, bmrank, BLACK, dbname);
			_wfopen_s(&fp, dbname, L"wb");
			fwrite(currentDb_b, 1, (size_t)memsize, fp);
			fclose(fp);
		}


		// save DTM
		memsize = GetDatabaseSize<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank);

		SetDbDTMName(wm, wk, bm, bk, wmrank, bmrank, WHITE, dbname);
		if(_wfopen_s(&fp, dbname, L"wb") != 0)
		{
			_get_errno(&error);
		}

		fwrite(dtm_W, 1, (size_t)memsize, fp);
		fclose(fp);

		if(!symmetric)
		{
			SetDbDTMName(wm, wk, bm, bk, wmrank, bmrank, BLACK, dbname);
			_wfopen_s(&fp, dbname, L"wb");
			fwrite(dtm_B, 1, (size_t)memsize, fp);
			fclose(fp);
		}

		WATCH_END(T_FILE_WRITE);

		// tidy up
		DeleteStates(wm, wk, bm, bk, wmrank, bmrank, state);

		return 1;
	}


	 // BuildSubDb
	////////////////////////////////////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxField, int BitsPerRow, int MaxRank> 
	int BuildSubDb(int wm,int wk,int bm,int bk)
	{
		int bmrank, wmrank; // hold the maximal rank of the white and black men
	
		if((bm+bk==0) || (wm+wk==0)) 
			return 0;

		if(wm+wk<bm+bk)
			return 0;

		if(wm+wk>MAXPIECE)
			return 0;

		if(wk+wm == bk+bm)
			{
			if(bk>wk)
				return 0;
			}

		if(wm == 0 && bm == 0)
			BuildSubDbSlice<BB, MaxField, BitsPerRow, MaxRank>(wm,wk,bm,bk,0,0);
	
		// only black has checkers
		if(wm!=0 && bm==0)
			{
			// bmrank >= (bm-1)/4 should take care of 5 pieces?!
				for(wmrank=MaxRank-1;wmrank>=((wm-1)/BitsPerRow);wmrank--)
					BuildSubDbSlice<BB, MaxField, BitsPerRow, MaxRank>(wm,wk,bm,bk,wmrank,0);
			}
	
		// only white has checkers
		if(bm!=0 && wm==0)
		{
			for(bmrank=MaxRank-1;bmrank>=((bm-1)/BitsPerRow);bmrank--)
				BuildSubDbSlice<BB, MaxField, BitsPerRow, MaxRank>(wm,wk,bm,bk,0,bmrank);
		}

		// both sides have checkers
		if(wm!=0 && bm!=0)
		{
			for(wmrank=MaxRank-1;wmrank>=((wm-1)/BitsPerRow);wmrank--)
			{
				for(bmrank=MaxRank-1;bmrank>=((bm-1)/BitsPerRow);bmrank--)
					BuildSubDbSlice<BB, MaxField, BitsPerRow, MaxRank>(wm,wk,bm,bk,wmrank,bmrank);
			}
		}

		return 1;
	}


	 // BuildDb
	////////////////////////////////////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxField, int BitsPerRow, int MaxRank> 
	int BuildDb(int n)
	{

		int bm=0,bk=0,wm=0,wk=0;
		int div1, div2, div3;
	
		for(div1=0;div1<=n;div1++)
		{
			for(div2=div1;div2<=n;div2++)
			{
				for(div3=div2;div3<=n;div3++)
				{
					//wm=div1;
					//wk=div2-div1;
					//bm=div3-div2;
					//bk=n-div3;
					bm=div1;
					bk=div2-div1;
					wm=div3-div2;
					wk=n-div3;
					BuildSubDb<BB, MaxField, BitsPerRow, MaxRank>(wm,wk,bm,bk);
				}
			}
		}
	
		return 1;
	}


	 // BuildEndgameDatabase
	////////////////////////////////////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxField, int BitsPerRow, int MaxRank> 
	bool BuildEndgameDatabase(int maxStones, eCheckersType checkersType)
	{
		iGeneratorLog = LogInit("c:\\EndGameGenerator.log");

		PRINT(L"Process started...");

		try
		{
			// allocate the global movegen configuration
			g_ED = new SEngineData();
			memset(g_ED, 0, sizeof(SEngineData));

			// set bitboard generators
			SetCheckersTypeMoveGen(g_ED, checkersType);

			// now the database building starts - build the 2, then 3, then 4, etc. -piece database
			for(int n=2; n<= maxStones; n++)
			{
				BuildDb<BB, MaxField, BitsPerRow, MaxRank>(n);
			}

			// clean up
			delete g_ED;
			g_ED = NULL;
		}
		catch(TerminationException*)
		{
			PRINT(L"Termination....");

			// clean up
			delete g_ED;
			g_ED = NULL;

			return false;
		}

		LogClose(iGeneratorLog);

		PRINT(L"Work done.");

		return true;
	}
}
